﻿using IEPI.App.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Heroius.DBTransitor
{
    public partial class FormMain : Form
    {
        internal FormMain()
        {
            InitializeComponent();
            #region Initialize Ultility
            SourceConnectionString = new SqlConnectionStringBuilder();
            TargetConnectionString = new SqlConnectionStringBuilder();
            SourceConnection = new SqlConnection();
            TargetConnection = new SqlConnection();
            HostStep1 = new IniConfigHost();
            SecSrc = HostStep1.Define("Source");
            ItemSrcString = SecSrc.Define("ConnectionString", "");
            ItemSrcTable = SecSrc.Define("TableName", "");
            SecTgt = HostStep1.Define("Target");
            ItemTgtString = SecTgt.Define("ConnectionString", "");
            ItemTgtTable = SecTgt.Define("TableName", "");
            #endregion
            BtnPrev.Enabled = false;
            Fields = new List<MappingField>();
        }

        #region Ultility

        /// <summary>
        /// database connectionstring builders, modified in FormStringBuilder forms.
        /// </summary>
        SqlConnectionStringBuilder SourceConnectionString, TargetConnectionString;
        /// <summary>
        /// database conncections, initialized in step2
        /// </summary>
        SqlConnection SourceConnection, TargetConnection;

        /// <summary>
        /// for step1, import/export ini config host, 
        /// <para>see IEPI.App.Utility docs</para>
        /// </summary>
        IniConfigHost HostStep1;
        /// <summary>
        /// for step1, in hoststep1 source / target config section
        /// <para>see IEPI.App.Utility docs</para>
        /// </summary>
        IniConfigSection SecSrc, SecTgt;
        /// <summary>
        /// for step1, in secsrc/sectgt, the items relative to ui elements
        /// <para>see IEPI.App.Utility docs</para>
        /// </summary>
        IniConfigItem<string> ItemSrcString, ItemTgtString, ItemSrcTable, ItemTgtTable;

        #endregion

        #region Page Navigating Control

        /// <summary>
        /// navigating direction, set in MoveXX methods
        /// </summary>
        bool MovingForward = true;

        private void BtnPrev_Click(object sender, EventArgs e)
        {
            MovePrevious();
        }

        private void BtnNext_Click(object sender, EventArgs e)
        {
            MoveNext();
        }

        void MovePrevious()
        {
            MovingForward = false;
            GuideSelecting = true;
            TcMe.SelectedIndex = TcMe.SelectedIndex - 1;
        }

        void MoveNext()
        {
            MovingForward = true;
            if (PreAction())
            {
                GuideSelecting = true;
                TcMe.SelectedIndex = TcMe.SelectedIndex + 1;
            }
        }

        /// <summary>
        /// 向Tab页面切换动作指示当前动作是否是由向导窗体发出的（否则为用户手动切换）
        /// </summary>
        bool GuideSelecting = false;

        private void TcMe_Selecting(object sender, TabControlCancelEventArgs e)
        {
            if (GuideSelecting)
            {
                GuideSelecting = false;
                BtnPrev.Enabled = e.TabPageIndex != 0;
                BtnNext.Enabled = e.TabPageIndex != 3;
            }
            else e.Cancel = true;
        }

        #endregion

        #region Paging Action

        bool PreAction()
        {
            switch (TcMe.SelectedIndex)
            {
                case 0:
                    #region Step 1 to 2 Pre-action
                    if (Step1HasEmpty)
                    {
                        MessageBox.Show(this, "信息不满足下一步！");
                        return false;
                    }
                    else if (IsStep1sImported)//check availability of imported info
                    {
                        try
                        {
                            //todo:check imported info's availability
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(this, ex.Message);
                            return false;
                        }
                    }
                    #endregion
                    #region Step 1 to 2 Pre-action part ii
                    //refill mapping fields: order sensitive
                    DgvSrcStruc.Rows.Clear();
                    DgvTgtStruc.Rows.Clear();
                    ColMapping.Items.Clear();
                    //read table structures and fill dgv
                    try
                    {
                        SourceConnection.ConnectionString = SourceConnectionString.ConnectionString;
                        SqlCommand SourceSelection = new SqlCommand(string.Format("SELECT * FROM [{0}]", TbSourceTable.Text));
                        SourceSelection.Connection = SourceConnection;
                        SourceConnection.Open();
                        SqlDataReader SourceReader = SourceSelection.ExecuteReader(System.Data.CommandBehavior.KeyInfo);
                        DataTable SourceSchema = SourceReader.GetSchemaTable();
                        SourceConnection.Close();

                        foreach (DataRow SrcRow in SourceSchema.Rows)
                        {
                            string Constrainsb = string.Empty;
                            if (Convert.ToBoolean(SrcRow["AllowDBNull"])) Constrainsb = "Nullable";
                            int i = DgvSrcStruc.Rows.Add(SrcRow["ColumnName"], SrcRow["DataTypeName"], Constrainsb.ToString());
                            ColMapping.Items.Add(SrcRow["ColumnName"]);
                            if ((bool)SrcRow["IsKey"])
                                DgvSrcStruc.Rows[i].Cells[0].Style.ForeColor = Color.Red;
                        }
                        //20121205: string array cache
                        SourceFieldNames = new string[ColMapping.Items.Count];
                        for (int i = 0; i < ColMapping.Items.Count; i++)
                        {
                            SourceFieldNames[i] = ColMapping.Items[i].ToString();
                        }

                        TargetConnection.ConnectionString = TargetConnectionString.ConnectionString;
                        SqlCommand TargetSelection = new SqlCommand(string.Format("SELECT * FROM [{0}]", TbTargetTable.Text));
                        TargetSelection.Connection = TargetConnection;
                        TargetConnection.Open();
                        SqlDataReader TargetReader = TargetSelection.ExecuteReader(System.Data.CommandBehavior.KeyInfo);
                        DataTable TargetSchema = TargetReader.GetSchemaTable();
                        TargetConnection.Close();

                        foreach (DataRow TgtRow in TargetSchema.Rows)
                        {
                            string Constrainsb = string.Empty;
                            if (Convert.ToBoolean(TgtRow["AllowDBNull"])) Constrainsb = "Nullable";

                            int i = DgvTgtStruc.Rows.Add(TgtRow["ColumnName"], TgtRow["DataTypeName"], Constrainsb.ToString());
                            if ((bool)TgtRow["IsKey"])
                                DgvTgtStruc.Rows[i].Cells[0].Style.ForeColor = Color.Red;
                        }
                    }
                    catch (Exception ex1)
                    {
                        SourceConnection.Close();
                        TargetConnection.Close();
                        MessageBox.Show(this, ex1.Message);
                        return false;
                    }
                    #endregion
                    break;
                case 1:
                    break;
                case 2:
                    if (Chb3Record.Checked && Tb3Record.Text == "")
                    {
                        MessageBox.Show(this, "记录规则尚未添加！");
                        return false;
                    }
                    if (Chb3Field.Checked && List3Field.Items.Count == 0)
                    {
                        MessageBox.Show(this, "字段规则尚未添加！");
                        return false;
                    }
                    break;
            }
            return true;
        }

        void AfterAction(object sender, EventArgs e)
        {
            if (MovingForward)//只处理没有异常检查的操作
            {
                switch (TcMe.SelectedIndex)
                {
                    case 1:
                        break;
                    case 2:
                        //rebuild mapping fields
                        Fields.Clear();
                        foreach (DataGridViewRow Row in DgvTgtStruc.Rows)
                        {
                            int SrcIndice;
                            object MappingValue = Row.Cells[3].Value;
                            if (MappingValue == null) SrcIndice = -1;
                            else SrcIndice = ColMapping.Items.IndexOf(MappingValue);
                            Fields.Add(new MappingField(Row.Cells[0].Value.ToString(),
                               (SqlDbType)Enum.Parse(typeof(SqlDbType), Row.Cells[1].Value.ToString(), true),
                                Row.Cells[2].Value.ToString().Contains("Nullable"), SrcIndice));
                        }
                        //UI reset
                        Rb3RecordProcess.Checked = true;
                        List3Field.Items.Clear();
                        Tb3Record.Clear();
                        Chb3Field.Checked = false;
                        Chb3Record.Checked = false;
                        Btn3FEdit.Enabled = false;
                        Btn3FDel.Enabled = false;
                        break;
                    case 3:
                        LbIcPassed.Text = LbIcProcessed.Text = "0";
                        LbDirect.Text = string.Format("从[{0}]向[{1}]", TbSourceTable.Text, TbTargetTable.Text);
                        break;
                }
            }
        }

        #endregion

        #region Single Tab Control

        #region Step 1: locating table

        bool Step1HasEmpty
        {
            get
            {
                return string.IsNullOrEmpty(TbSourceBase.Text) ||
                    string.IsNullOrEmpty(TbSourceTable.Text) ||
                    string.IsNullOrEmpty(TbTargetBase.Text) ||
                    string.IsNullOrEmpty(TbTargetTable.Text);
            }
        }

        bool SourceTableIsImported = false, TargetTableIsImported = false;
        bool IsStep1sImported
        {
            get { return SourceTableIsImported || TargetTableIsImported; }
        }

        private void BtnSouceBase_Click(object sender, EventArgs e)
        {
            bool Load = !string.IsNullOrEmpty(TbSourceBase.Text);
            if (new FormStringBuilder("源 SQL Server 连接", SourceConnectionString, Load).ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
                TbSourceBase.Text = SourceConnectionString.ConnectionString;
        }

        private void BtnTargetBase_Click(object sender, EventArgs e)
        {
            bool Load = !string.IsNullOrEmpty(TbTargetBase.Text);
            if (new FormStringBuilder("目标 SQL Server 连接", TargetConnectionString, Load).ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
                TbTargetBase.Text = TargetConnectionString.ConnectionString;
        }

        private void BtnSourceTable_Click(object sender, EventArgs e)
        {
            //todo:browse table
            SourceTableIsImported = false;
        }

        private void BtnTargetTable_Click(object sender, EventArgs e)
        {
            //todo:browse table
            TargetTableIsImported = false;
        }

        private void BtnImportDestiny_Click(object sender, EventArgs e)
        {
            DiaImportStep1.ShowDialog(this);
        }

        private void DiaImportStep1_FileOk(object sender, CancelEventArgs e)
        {
            try
            {
                HostStep1.Load(DiaImportStep1.FileName, false,
                    (c) => { return !string.IsNullOrEmpty(c.Value); });
                SourceConnectionString.ConnectionString = ItemSrcString.Value;
                TargetConnectionString.ConnectionString = ItemTgtString.Value;
                SourceTableIsImported = true;
                TargetTableIsImported = true;
                TbSourceBase.Text = ItemSrcString.Value;
                TbSourceTable.Text = ItemSrcTable.Value;
                TbTargetBase.Text = ItemTgtString.Value;
                TbTargetTable.Text = ItemTgtTable.Value;
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, ex.Message);
            }
        }

        private void BtnExportDestiny_Click(object sender, EventArgs e)
        {
            if (Step1HasEmpty)
            { MessageBox.Show(this, "信息不足"); }
            else
                DiaExportStep1.ShowDialog(this);
        }

        private void DiaExportStep1_FileOk(object sender, CancelEventArgs e)
        {
            HostStep1.Load(DiaExportStep1.FileName, AutoFill: true);
            ItemSrcString.Refresh(TbSourceBase.Text);
            ItemSrcTable.Refresh(TbSourceTable.Text);
            ItemTgtString.Refresh(TbTargetBase.Text);
            ItemTgtTable.Refresh(TbTargetTable.Text);
        }

        #endregion

        #region Step2: mapping fields

        /// <summary>
        /// 一个目标数据表字段的列表，包含列名、数据类型、约束及映射源字段索引
        /// </summary>
        List<MappingField> Fields;

        private void DgvTgtStruc_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            MessageBox.Show(this, e.Exception.Message);
        }

        #endregion

        #region Step3: rules

        #region Record

        RecordRule RecordRules;

        string[] SourceFieldNames;

        private void Chb3Record_CheckedChanged(object sender, EventArgs e)
        {
            Pnl3Record.Enabled = Chb3Record.Checked;
        }

        private void Btn3REdit_Click(object sender, EventArgs e)
        {
            FormExpressionBuilder exp =
            new FormExpressionBuilder(SourceFieldNames, Tb3Record.Text);
            exp.OnProcedureGenerated += (s, ee) =>
            {
                Tb3Record.Text = exp.LatestExpression;
                RecordRules = new RecordRule(Rb3RecordProcess.Checked,
                exp.LatestProc);
            };
            exp.ShowDialog(this);
        }

        #endregion

        #region Field

        private void Chb3Field_CheckedChanged(object sender, EventArgs e)
        {
            Pnl3Field.Enabled = Chb3Field.Checked;
        }

        private void Btn3FAdd_Click(object sender, EventArgs e)
        {
            FormMappingRule FieldRuler = new FormMappingRule(SourceFieldNames, Fields);
            FieldRuler.RuleUpdated += (s, ee)=>
                {
                    List3Field.Items.Add(ee.FieldName);
                };
            FieldRuler.ShowDialog(this);
        }

        private void Btn3FEdit_Click(object sender, EventArgs e)
        {
            string Fn = List3Field.SelectedItem.ToString();
            FormMappingRule FieldRuler = new FormMappingRule(SourceFieldNames, Fields.Find(f => f.Name == Fn));
            FieldRuler.ShowDialog(this);
        }

        private void Btn3FDel_Click(object sender, EventArgs e)
        {
            string Fn = List3Field.SelectedItem.ToString();
            Fields.Find(f => f.Name == Fn).RemoveRule();
            List3Field.Items.RemoveAt(List3Field.SelectedIndex);
        }

        private void List3Field_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (List3Field.SelectedIndex == -1)
            {
                Btn3FDel.Enabled = false;
                Btn3FEdit.Enabled = false;
            }
            else
            {
                Btn3FDel.Enabled = true;
                Btn3FEdit.Enabled = true;
            }
        }

        #endregion

        #endregion

        #region Step4: execution

        /// <summary>
        /// async task
        /// </summary>
        Task TskExec;
        /// <summary>
        /// cancelling support
        /// </summary>
        CancellationTokenSource TskCanceller;

        /// <summary>
        /// the target fields count, set in afterpageaction by dgvtgtstruc (which is specified in step2)
        /// </summary>
        int FieldsCount { get { return Fields.Count; } }
        /// <summary>
        /// Item count during execution the transmission, of Processed / Passed
        /// <para>reset in execaction when each time call to execute</para>
        /// </summary>
        int IcProcessed, IcPassed;

        private void BtnExec_Click(object sender, EventArgs e)
        {
            TskCanceller = new CancellationTokenSource();
            TskExec = new Task(ExecAction, TskCanceller.Token);
            TskExec.ContinueWith(ExecSucceed, TaskContinuationOptions.OnlyOnRanToCompletion);
            TskExec.ContinueWith(ExecCanceled, TaskContinuationOptions.OnlyOnCanceled);
            TskExec.ContinueWith(ExecErr, TaskContinuationOptions.OnlyOnFaulted);
            BtnExec.Enabled = false;
            BtnPrev.Enabled = false;
            TskExec.Start();
        }

        private void BtnTerminate_Click(object sender, EventArgs e)
        {
            TskCanceller.Cancel();
        }

        /// <summary>
        /// Execution body for task to call, transmit data
        /// </summary>
        void ExecAction()
        {
            IcProcessed = IcPassed = 0;

            SqlCommand SourceSelection = SourceConnection.CreateCommand();
            SourceSelection.CommandText = string.Format("SELECT * FROM {0}", TbSourceTable.Text);
            SourceConnection.Open();
            SqlDataReader SourceReader = SourceSelection.ExecuteReader();

            SqlCommand TargetSelection = TargetConnection.CreateCommand();
            TargetSelection.CommandText = string.Format("SELECT * FROM {0}", TbTargetTable.Text);
            SqlDataAdapter TargetAdapter = new SqlDataAdapter(TargetSelection);
            SqlCommandBuilder TargetCommandBuilder = new SqlCommandBuilder(TargetAdapter);
            DataTable TargetData = new System.Data.DataTable();
            TargetAdapter.Fill(TargetData);

            int i = 0;
            while (SourceReader.Read())
            {
                //cancel check
                if (TskCanceller.IsCancellationRequested)
                {
                    SourceConnection.Close();
                    TargetConnection.Close();
                    return;
                }

                object[] SourceValues = new object[SourceReader.FieldCount];
                SourceReader.GetValues(SourceValues);

                if ((!Chb3Record.Checked) || RecordRules.Validate(SourceReader))
                {
                    DataRow RowMe = TargetData.NewRow();//data cache
                    for (int j = 0; j < FieldsCount; j++)
                    {
                        if (Chb3Field.Checked)
                            RowMe[j] = Fields[j].GetRuledValue(SourceReader);
                        else
                            RowMe[j] = SqlDataConverter.ChangeType(
                                (Fields[j].HasMapping) ? SourceReader[Fields[j].MappingIndex] : null,
                                Fields[j].SQLType, Fields[j].Nullable);
                    }
                    TargetData.Rows.Add(RowMe);
                    UpdateProcessIc();
                }
                else
                {
                    UpdatePassedIc(i);
                }
                i++;
            }

            TargetAdapter.Update(TargetData);

            SourceConnection.Close();
            TargetConnection.Close();
        }

        /// <summary>
        /// update the items count label.text properties using IcProcess field
        /// <para>the IcProcess will increase automatically each time calling this method</para>
        /// </summary>
        void UpdateProcessIc()
        {
            if (InvokeRequired)//call from out of Form thread
            { Invoke(new Action(UpdateProcessIc)); }
            else
            {
                IcProcessed++;
                LbIcProcessed.Text = IcProcessed.ToString();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        void UpdatePassedIc(int i)
        {
            if (InvokeRequired)
            { Invoke(new Action<int>(UpdatePassedIc), i); }
            else
            {
                IcPassed++;
                LbIcPassed.Text = IcPassed.ToString();
                TbExecOut.AppendText(string.Format("在行数 {0} 处略过记录\r\n", i));
            }
        }

        #region Continue with Task

        void ExecSucceed(Task TheTask)
        {
            if (InvokeRequired) { Invoke(new Action<Task>(ExecSucceed), TheTask); }
            else
            {
                MessageBox.Show(this, "操作成功！");
                BtnExec.Enabled = true;
                BtnPrev.Enabled = true;
            }
        }

        void ExecCanceled(Task TheTask)
        {
            if (InvokeRequired) { Invoke(new Action<Task>(ExecCanceled), TheTask); }
            else
            {
                SourceConnection.Close();
                TargetConnection.Close();
                MessageBox.Show(this, "操作已取消！");
                BtnExec.Enabled = true;
                BtnPrev.Enabled = true;
            }
        }

        void ExecErr(Task TheTask)
        {
            if (InvokeRequired) { Invoke(new Action<Task>(ExecErr), TheTask); }
            else
            {
                SourceConnection.Close();
                TargetConnection.Close();
                MessageBox.Show(this, TheTask.Exception.Message);
                BtnExec.Enabled = true;
                BtnPrev.Enabled = true;
            }
        }

        #endregion

        #endregion

        #endregion

        #region Closing Validation

        private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (TskExec != null && TskExec.Status == TaskStatus.Running)
            {
                MessageBox.Show(this, "动作正在执行，请等待完成或取消动作后再试！");
                e.Cancel = true;
            }
        }

        private void FormMain_FormClosed(object sender, FormClosedEventArgs e)
        {
            SourceConnection.Close();
            TargetConnection.Close();
        }

        #endregion

    }
}
